package vectors;

public class Matrix {
	
	private int width;	// number of columns vectors
	private int height;	// size of column vector
	
	private double[][] values;	// All the values
	
	/**
	 * Constructs a matrix with the given values.
	 * @param values  the initial values for the matrix
	 */
	public Matrix(double[][] values){
		this.values = values;
		this.width = values.length;
		if(width<1){
			throw new IllegalArgumentException("Width cannot be zero");
		}
		this.height = values[0].length;
		if(height<1){
			throw new IllegalArgumentException("Height cannot be zero");
		}
		for(int i=0;i<width;i++){
			if(values[i].length != height){
				throw new IllegalArgumentException("Height of column "+i+" ("+values[i].length+"does not match the height of the first column ("+height+")");
			}
		}
	}

	/**
	 * Multiplies this matrix by the given vector.
	 * 
	 * @param input  the input vector (for neural nets, the inhibited vector)
	 * @return  the resulting vector
	 */
	public Vector times(Vector input) {
		double[] output = new double[getWidth()];
		for(int i=0;i<getWidth();i++){
			output[i] = getCol(i).dot(input);
		}
		return new Vector(output);
	}
	
	/**
	 * Gets a column of the matrix as a Vector. Faster than getRow
	 * 
	 * @param colNum  index for the column
	 * @return  the column as a Vector
	 */
	public Vector getCol(int colNum){
		return new Vector(values[colNum]);
	}
	
	/**
	 * Gets a row of the matrix as a Vector. Slower than getCol
	 * 
	 * @param rowNum  index for the row
	 * @return  the row as a Vector
	 */
	public Vector getRow(int rowNum){
		double[] values = new double[width];
		for(int i=0;i<width;i++){
			values[i] = getValue(i,rowNum);
		}
		return new Vector(values);
	}

	/**
	 * A string representation of this matrix
	 * 
	 * @return  the vector as: [v11][v12] ... [v1W]
	 *                         [v21][v22] ... [v2W]
	 *                          ...  ...  ...  ...
	 *                         [vH1][vH2] ... [vHW]
	 */
	public String toString(){
		StringBuffer str = new StringBuffer();
		for(int i=0;i<getHeight();i++){
			for(int j=0;j<getWidth();j++){
				str.append("[").append(getValue(j,i)).append("]");
			}
			str.append("\n");
		}
		return str.toString();
	}
	
//###################### Regular getters and Setters #############################
	
	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}
	
	public double getValue(int i, int j){
		return values[i][j];
	}
	
	public void setValue(int i, int j, double value){
		values[i][j] = value;
	}

	//####################### Static Functions ########################
	
	/**
	 * Constructs random values for a matrix with values between -1.0 and 1.0.
	 * 
	 * @param width
	 * @param height
	 * @return  the newly created matrix
	 */
	public static double[][] randomVals(int width, int height){
		double[][] values = new double[width][height];
		for(int i=0;i<width;i++){
			double[] v = new double[height];
			for(int j=0;j<height;j++){
				v[j] = Math.random()*2-1;
			}
			values[i] = v;
		}
		return values;
	}
	
	/**
	 * Creates a new random matrix with values between -1.0 and 1.0.
	 * When working with neural nets, should give size of "next" layer as width and "previous" layer as height
	 * 
	 * @param width
	 * @param height
	 * @return
	 */
	public static Matrix random(int width, int height){
		return new Matrix(Matrix.randomVals(width, height));
	}

}
